﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;
using Microsoft.Practices.Unity.InterceptionExtension;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;

namespace Thallo {
    public sealed class InvocationContext : IDisposable {
        internal static event EventHandler<EventArgs> Invoked;
        internal static event EventHandler<EntityUpdateEventArgs> DataChanged;

        static ThreadLocal<InvocationContext> _current = new ThreadLocal<InvocationContext>();
        private ICacheManager Store { get; set; }
        MethodDescriptor _mi;

        public static InvocationContext Current {
            get { return _current.Value; }
        }

        IMethodInvocation _input;
        internal InvocationContext(IMethodInvocation input) {
            _current.Value = this;
            _input = input;
            Store = BackingStoreManager.GetStore();
            _mi = TypeInfoCache.GetMethod(input.MethodBase);
        }

        string _identity;
        internal string Identity {
            get {
                if (_identity == null)
                    _identity = Helper.GetKey(_input.MethodBase, _input.Inputs);
                return _identity;
            }
        }

        internal MethodDescriptor MethodInfo {
            get {
                return _mi;
            }
        }

        internal void RemoveValue() {
            RemoveValue(Identity);
        }

        internal void RemoveValue(string key) {
            Store.Remove(key);
        }

        internal void SetValue(MethodResult value) {
            RemoveValue();
            if (value != null) {
                var time =  MethodInfo.ExpirationTime;
                if( time <=0)
                    time = Thallo.Configuration.ThalloSection.Current.ExpirationTime;
                if (time > 0) {
                    var exp2 = new DependencyExpiration(MethodInfo);
                    var exp1 = new AbsoluteTime(DateTime.Now.AddMilliseconds(time));
                    Store.Add(Identity, value, CacheItemPriority.Normal, null, exp1,exp2);
                } else {
                    Store.Add(Identity, value, CacheItemPriority.Normal, null, new DependencyExpiration(MethodInfo));
                }
                ReturnValue = value.ReturnValue;
            }
        }

        internal void MethodInvoked() {
            if (Invoked != null)
                Invoked.Invoke(this, EventArgs.Empty);
        }

        public void EntityChange<T>(T value) where T : class {
            if (DataChanged != null) {
                DataChanged(this, new EntityUpdateEventArgs() { EntityType = typeof(T), Value = value });
            }
        }

        internal MethodResult GetValue() {
            return Store.GetData(Identity) as MethodResult;
        }

        object _returnValue = null;
        public object ReturnValue {
            get {
                var item = GetValue();
                if (item != null)
                    _returnValue = item.ReturnValue;
                return _returnValue;
            }
            private set {
                _returnValue = value;
            }
        }

        internal bool ReturnValueHasChanged { get { return _returnValue != null; } }

        public MethodBase MethodBase { get { return _input.MethodBase; } }

        public object[] Inputs { get { return _input.Inputs.GetValues().ToArray(); } }

        public void Dispose() {
            _current.Value = null;
        }
    }

    class EntityUpdateEventArgs : EventArgs {
        public EntityUpdateEventArgs() {

        }

        public Type EntityType { get; set; }

        public object Value { get; set; }
    }
}
